/*
 * Copyright 2012 Christian Schindelhauer, Peter Thiemann, Faisal Aslam, Luminous Fennell and Gidon Ernst.
 * All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * only, as published by the Free Software Foundation.
 * 
 * This code is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 3 for more details (a copy is
 * included in the LICENSE file that accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License
 * version 3 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Faisal Aslam 
 * (faisal.aslam AT gmail.com)
 * if you need additional information or have any questions.
 */
package takatuka.classreader.dataObjs.attribute;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;

import takatuka.classreader.dataObjs.*;
import takatuka.classreader.logic.constants.*;
import takatuka.classreader.logic.factory.*;
import takatuka.classreader.logic.file.*;
import takatuka.classreader.logic.util.*;

/**
 * <p>Title: </p>
 * <p>Description:
 * Based on the structure defined at 
 * http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#43817
 * </p>
 * @author Gidon Ernst, Jet Tang and Faisal Aslam
 * @version 1.0
 */
public class BytecodeProcessor {

    private static FactoryFacade factory = FactoryPlaceholder.getInstanceOf().
            getFactory();
    private static final String OPPRAM = "opcode-parameters.properties";
    private static PropertyReader propReader = PropertyReader.getInstanceOf();
    private static TreeSet<Integer> fixedunUsedInstruction = getAllPossibleOpCodes();
    private static Properties opcodeToParams = propReader.loadProperties(OPPRAM);
    private int instAddress = 0;
    private static Instruction previousInstruction = null;
    private static CodeAtt currentCodeAttr = null;

    public BytecodeProcessor() {
    }

    public static TreeSet<Integer> getAllPossibleOpCodes() {
        TreeSet<Integer> ret = new TreeSet();
        for (int loop = 0; loop < 256; loop++) {
            ret.add(loop);
        }
        return ret;
    }

    /**
     * An instruction could have multiple parameters/operands.  This function return
     * aggregated size of all of those parameters.
     * 
     * @param opcode
     * @return
     */
    public static int getTotalParameterSize(int opcode) {
        String parameterSizes = opcodeToParams.getProperty(opcode + "");
        int totalSize = 0;
        try {
            String[] paramSizesArray = parameterSizes.split(",");
            for (int loop = 0; loop < paramSizesArray.length; loop++) {
                totalSize += Integer.parseInt(paramSizesArray[loop]);
            }
        } catch (NullPointerException d) {
            System.out.println(" null pointer exception with opcode=" + opcode);
            System.exit(1);
        }
        return totalSize;
    }

    public static Vector<Integer> getAllParameterSizes(int opcode) {
        String parameterSizes = opcodeToParams.getProperty(opcode + "");
        Vector<Integer> ret = new Vector<Integer>();
        String[] paramSizesArray = parameterSizes.split(",");
        int totalSize = 0;
        for (int loop = 0; loop < paramSizesArray.length; loop++) {
            ret.addElement(Integer.parseInt(paramSizesArray[loop]));
        }
        return ret;
    }


    /**
     * the offSet we accept here is not start of an instruction but start of instruction plus one
     * @param code
     * @param offSet
     * @param jumpAddresses
     * @return
     * @throws java.lang.Exception
     */
    public final int processCodeLookUpSwitch(byte code[], int offSet,
            Vector jumpAddresses) throws
            Exception {
        ByteBuffer bf = ByteBuffer.wrap(code);
        int currentInstAddress = offSet - 1;
        int i;
        long default_address = 0;
        long padding = (offSet) % 4;
        if (padding > 0) {
            padding = 4 - padding;
        }
        offSet += padding;

        default_address = bf.getInt(offSet); //get32(code, offSet + 0);
        jumpAddresses.addElement((int) (default_address));
        long pair_count = bf.getInt(offSet + 4); //get32(code, offSet + 4);

        offSet += 8;

        for (i = 0; i < pair_count; i++) {
            long key = bf.getInt(offSet + i * 8); //get32(code, offSet + i * 8);
            long jump_address = bf.getInt(offSet + 4 + i * 8); //get32(code, offSet + 4 + i * 8);
            jumpAddresses.addElement((int) (jump_address));
            /*Miscellaneous.println("\tkey[" + i + "] = " + key +
            ", jump_address[" + i + "] = " +
            jump_address);*/
        }
        offSet += pair_count * 8;
        return offSet;

    }

    /**
     * the offSet we accept here is not start of an instruction but start of instruction plus one
     * @param code
     * @param offSet
     * @param jumpAddresses
     * @return
     * @throws java.lang.Exception
     */
    public final int processTableSwitch(byte code[], int offSet,
            Vector jumpAddresses) throws Exception {
        ByteBuffer bf = ByteBuffer.wrap(code);
        //Un debug = factory.createUn();
        int currentInstAddress = offSet - 1;
        // Miscellaneous.println(" table swithc "+currentInstAddress);
        int i;
        long default_address = 0;
        int oldpc = offSet;
        long padding = (offSet) % 4;
        if (padding > 0) {
            padding = 4 - padding;
        }
        offSet += (int) padding;
        //Miscellaneous.println("----> padding = "+padding);
        //debug = factory.createUn(0).trim((int)padding);
        default_address = bf.getInt(offSet);//get32(code, offSet + 0);
        //debug.conCat(new Un((int)default_address));

        //Miscellaneous.println("----> default address = "+default_address);

        jumpAddresses.addElement((int) (default_address));


        long low = bf.getInt(offSet + 4);//get32(code, offSet + 4);
        long high = bf.getInt(offSet + 8); //get32(code, offSet + 8);
        //debug.conCat(new Un((int)low));
        //debug.conCat(new Un((int)high));
        long jump_count = high - low + 1;

        offSet += 12;
        for (i = 0; i < jump_count; i++) {
            long jump_address = bf.getInt(offSet + i * 4); // get32(code, offSet + i * 4);
            jumpAddresses.addElement((int) (jump_address));
            //debug.conCat(new Un((int)jump_address));
            //Miscellaneous.println("----> jump addresses = "+jumpAddresses);

//            Miscellaneous.println("\tjump_address[" + i + "] = " +
//                               jump_address);
        }
        offSet += (int) jump_count * 4;
        //Un un = setTableSwitchAddreses(debug, jumpAddresses);
        //Miscellaneous.println("I am here xxxxx = "+un+ ",   "+debug+ ", "+un.equals(debug));
        return offSet;
    }

    private static byte[] getOperandsData(byte code[], int pc,
            int numberOfOperands) {
        byte data[] = new byte[numberOfOperands];
        for (int loop = 0; loop < numberOfOperands; loop++) {
            data[loop] = code[pc++];
        }
        return data;
    }

    private int getOperands(byte code[], int pc, int numberOfOperands,
            Un operand) throws Exception {
        byte data[] = getOperandsData(code, pc, numberOfOperands);
        operand.setData(data);
        return pc + numberOfOperands;
    }

    public Vector process(byte code[], CodeAtt codeAtt) throws
            Exception {
        int pc = 0;
        int opcode;
        Un operands = null;
        Vector instructions = new Vector();
        Instruction inst = null;
        instAddress = 0;
        currentCodeAttr = codeAtt;
        // Miscellaneous.println(ClassFile.currentClassToWorkOn.getClassName());
        while (pc < code.length) {
            operands = new Un(); //bad but they have such byte//factory.createUn();
            int instruction = unsigned(code[pc]);
            opcode = instruction;
            // Miscellaneous.println(offSet + ": " + getMnemonic(instruction));
            int i, old_pc;
            old_pc = pc;
            pc++; // the bytecode itself

            switch (instruction) {
                case JavaInstructionsOpcodes.TABLESWITCH:
                    pc = processTableSwitch(code, pc, new Vector());
                    operands.setData(getOperandsData(code, old_pc + 1, pc - (old_pc + 1)));
                    break;

                case JavaInstructionsOpcodes.LOOKUPSWITCH:
                    pc = processCodeLookUpSwitch(code, pc, new Vector());
                    operands.setData(getOperandsData(code, old_pc + 1, pc - (old_pc + 1)));
                    break;

                case JavaInstructionsOpcodes.WIDE:
                    if (unsigned(code[pc]) == JavaInstructionsOpcodes.IINC) {
                        pc = getOperands(code, pc, 4 + 1, operands); //1 of the incc instruction itself.
                    } else {
                        pc = getOperands(code, pc, 2 + 1, operands);
                    }
                    break;
                default:
                    pc = getOperands(code, pc, getTotalParameterSize(instruction),
                            operands);
                    break;
            }
            fixedunUsedInstruction.remove(opcode);
            previousInstruction = inst;
            inst = factory.createInstruction(opcode, operands, codeAtt);
            postInstrReadWork(inst, instructions);

        }
        previousInstruction = null;
        currentCodeAttr = null;
        //Miscellaneous.println("\n\n\n");
        //Miscellaneous.println(StringUtil.getString(instructions, "\n"));
        return instructions;
    }

    public static Instruction getPrevInstruction() {
        return previousInstruction;
    }

    public static CodeAtt getCurrentCodeAtt() {
        return currentCodeAttr;
    }

    public static void removeFromFixUnUsedInstruction(int opCode) {
        fixedunUsedInstruction.remove(opCode);
    }

    /**
     * 
     * After processing a instruction we may have to do following.
     * 1) Record that which opcode are used in the code
     * 2) Change instruction in any way or do some optimization
     * 3) Finally, add it in the instruction vector (or may be not)
     * The default implementation only add the instruction (3), (1) record
     * that the opcode is used
     * 
     * @param inst
     * @param instructions The instruction vector which has all the instructions
     */
    protected void postInstrReadWork(Instruction inst, Vector instructions) {
        instructions.addElement(inst);
        inst.setOffSet(instAddress);
        inst.setOriginalOffSet(instAddress);
        instAddress += inst.length();
    }

    public static TreeSet getUnUsedOpCodes() {
        return (TreeSet) fixedunUsedInstruction.clone();
    }

    private byte[] hexDecode(String code) {
        int length = code.length() / 2;
        byte result[] = new byte[length];

        for (int i = 0; i < length; i++) {
            int x0 = hexDecode(code.charAt(i * 2));
            int x1 = hexDecode(code.charAt(i * 2 + 1));
            // System.out.print(x0 + " ");
            // System.out.print(x1 + " ");
            result[i] = (byte) (x0 * 16 + x1);

            // System.out.print(result[i] + " ");
        }
        // Miscellaneous.println();

        return result;
    }

    private int hexDecode(char x) {
        if (x >= 'A' && x <= 'F') {
            return x + 10 - 'A';
        }
        if (x >= 'a' && x <= 'f') {
            return x + 10 - 'a';
        }
        return x - '0';
    }

    public final static void main(String arg[]) throws Exception {
        if (arg.length == 0) {
            Miscellaneous.println("usage: java BytecodeProcessor <input file>");
            Miscellaneous.exit();
        }
        BytecodeProcessor bcProc = factory.createBytecodeProcessor();
        BufferedReader in = new BufferedReader(new FileReader(arg[0]));

        while (true) {
            String line = in.readLine();
            if (line == null) {
                break;
            }
            // hexDecode( line );
            bcProc.process(bcProc.hexDecode(line), null);
            Miscellaneous.println();
            Miscellaneous.println();
        }
    }

    private static int unsigned(int i) {
        if (i < 0) {
            return i + 256;
        }
        return i;
    }
    /*
    private static long get32(byte[] code, int i) {
    long val =
    0x00000001 * unsigned(code[i + 3]) + 0x00000100 * unsigned(code[i + 2]) + 0x00010000 * unsigned(code[i + 1]) + 0x01000000 * unsigned(code[i + 0]);
    return val;
    }
     */
}
